package edu.pku.sei.sct.match;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.BasicEMap;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;

import edu.pku.sei.ocl.utilities.CacheUtil;

public class ModelManager {
	private EMap<String,EPackage> name2types = null;
	
	private HashMap<String,ModelInternalContainer> name2models = new HashMap<String,ModelInternalContainer>();
	private CacheUtil linkCache = new CacheUtil();
	private CacheUtil objectCache = new CacheUtil();

	
	public BasicEMap<String,Integer> name2id;
	public CacheUtil type2id = new CacheUtil();// [0] = model_name_id, [1] = type_id
	public CacheUtil model2id = new CacheUtil(); // [0] = model_name_id, [1] = type_id, [2] = obj_id
	
	public void setTypeMap(EMap<String,EPackage> name2types) {
		this.name2types = name2types;
	}
	
	public ModelInternalContainer getContainer(String name) {
		ModelInternalContainer res = name2models.get(name);
		
		if(res==null) {
			res = new ModelInternalContainer();
			name2models.put(name, res);
		}

		return res;
	}
	
	public EList<EObject> getAllObjects(String name) {
		return getContainer(name).getAllObjects();
	}
	
	public void initContainer(String name, Resource res) {
		if(res!=null)
			getContainer(name).init(res);
	}
	
	public void addObject(String name, EObject o) {
		getContainer(name).addObject(o);
		EClass cls = o.eClass();
		objectCache.put(null, name, cls);
		linkCache.clear();
	}
	
	public void removeObject(String name, EObject o) {
		getContainer(name).removeObject(o);
		EClass cls = o.eClass();
		objectCache.put(null, name, cls);
		linkCache.clear();
//		for(EReference ref : cls.getEAllReferences()) {
//			linkCache.put(null, ref);
//		}
	}
	
	@SuppressWarnings("rawtypes")
	public void addLink(EReference ref, EObject so, EObject to) {
		if(ref.isMany()) {
			((EList)so.eGet(ref)).add(to);
		} else {
			so.eSet(ref, to);
		}
		
		linkCache.put(null, ref);
	}
	
	@SuppressWarnings("rawtypes")
	public void removeLink(EReference ref, EObject so, EObject to) {
		if(ref.isMany()) {
			((EList)so.eGet(ref)).remove(to);
		} else {
			so.eSet(ref, null);
		}
		linkCache.put(null, ref);
	}
	
	public List<int[]> getLinkScope(EReference ref, String sn,  EClass st, String tn, EClass tt) {
		@SuppressWarnings("unchecked")
		List<int[]> result = (List<int[]>) linkCache.get(ref,sn,st,tn,tt);
		if(result == null) {
			result = collectLinkScope(ref,sn,st,tn,tt);
			linkCache.put(result, ref,sn,st,tn,tt);
		}
		return result;
	}
	
	@SuppressWarnings("rawtypes")
	private List<int[]> collectLinkScope(EReference ref, String sn,  EClass st, String tn, EClass tt) {
		List<int[]> result = new ArrayList<int[]>();
		List<Integer> srcIDs = getObjectIDs(sn, st);
		for(int sid : srcIDs) {
			EObject so = getObject(sn,sid);
			Object t = so.eGet(ref);
			if(t instanceof List) {
				int size = ((List) t).size();
				for(int i=0;i<size;i++) {
					EObject to = (EObject) ((List) t).get(i);
					if(tt.isSuperTypeOf(to.eClass())) {
						int tid = getObjectId(tn,to);
						result.add(new int[]{sid, tid});
					}
				}
				
			} else if(t instanceof EObject) {
				int tid = getObjectId(tn,(EObject) t);
				result.add(new int[]{sid,tid});
			} else if(t == null) {
				// current so.ref is null
			} else {
				System.out.println("unknown feature structure");
			}
		}
		
		return result;
	}

	public EObject getObject(String sn, int sid) {
		return getContainer(sn).getObject(sid);
	}

	public int getObjectId(String sn, EObject so) {
		return getContainer(sn).getObjectId(so);
	}
	
	public List<Integer> getObjectIDs(String sn, EClass st) {
		return getContainer(sn).getObjectIDs(st);
	}

	public int[] getObjectIDArray(String sn, EClass st) {
		int[] result = (int[]) objectCache.get(sn,st);
		if(result==null) {
			result = getContainer(sn).getObjectIDArray(st);
			objectCache.put(result, sn,st);
		}
		
		return result;
	}
	
	static private int objectId = 0;
	static protected int getNewID() {
		return objectId++;
	}

	public void save(Resource resource, ModelInternalContainer container) {
		for(EObject o : container.getAllObjects()) {
			if(o.eContainer()==null)
				resource.getContents().add(o);
		}
		
		try {
			HashMap<Object, Object> options = new HashMap<Object, Object>();
			options.put(XMLResource.OPTION_ENCODING, "utf-8");
			options.put(XMLResource.OPTION_SCHEMA_LOCATION, true);
			
			resource.save(options);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

class ModelInternalContainer {
	
	
	private EList<EObject> allObjects = new BasicEList<EObject>();
	
	public EList<EObject> getAllObjects() {
		return allObjects;
	}

	private HashMap<EClass, List<Integer>> classToIDs = new HashMap<EClass, List<Integer>>();
	
	private HashMap<EObject,Integer> obj2id = new HashMap<EObject,Integer>();
	private HashMap<Integer,EObject> id2obj = new HashMap<Integer,EObject>();
	
	
	public List<Integer> getObjectIDs(EClass c) {
		List<Integer> result = classToIDs.get(c);
		if(result==null) {
			result = new ArrayList<Integer>();
			classToIDs.put(c, result);
		}
		return result;
	}
	public int[] getObjectIDArray(EClass c) {
		List<Integer> ids = getObjectIDs(c);
		int[] result = new int[ids.size()];
		
		for(int i = 0; i<ids.size(); i++) {
			result[i] = ids.get(i);
		}

		return result;
	}
	
	public void addObject(EObject o){
		List<Integer> list = null;
		
		allObjects.add(o);
		
		int newID = ModelManager.getNewID();
		
		obj2id.put(o, newID);
		id2obj.put(newID, o);
		
		list = getObjectIDs(o.eClass());
		list.add(newID);
		
		EList<EClass> eAllSuperTypes = o.eClass().getEAllSuperTypes();
		for(EClass c : eAllSuperTypes) {
			list = getObjectIDs(c);
			list.add(newID);
		}
	}
	
	public void removeObject(EObject o) {
		List<Integer> list = null;
		
		allObjects.remove(o);
		EcoreUtil.remove(o);
		int oID = getObjectId(o);
		if(oID!=-1) {
			obj2id.put(o, null);
			id2obj.put(oID, null);
		}
		
		list = getObjectIDs(o.eClass());
		list.remove((Integer)oID);
		EList<EClass> eAllSuperTypes = o.eClass().getEAllSuperTypes();
		for(EClass c : eAllSuperTypes) {
			list = getObjectIDs(c);
			list.remove((Integer)oID);
		}
	}
	
	public void init(Resource res) {
		TreeIterator<EObject> it = res.getAllContents();
		while(it.hasNext()) {
			addObject(it.next());
		}
	}
	
	public int getObjectId(EObject o) {
		Integer i = obj2id.get(o);
		if(i==null)
			i=-1;
		return i;
	}
	
	public EObject getObject(int id) {
		return id2obj.get(id);
	}
	
}